前言
前一段时间Element 2.0发布,本着好奇就想了解着这个开源组件库的故事。那今天就来看看来自饿了么大前端@马冉在这个开源组件中关于可访问性的分享。
正文从这开始~
Element 是一套基于 Vue 的桌面端组件库,于 2016 年 9 月开源,目前已经成长为 Vue 社区最受欢迎的开源项目之一。不久前我们发布了它的 2.0 版本,在这个版本中,除了新增功能外,我们还着重提升了各个组件的可访问性。今天就为大家做一个介绍。
tabindex
键盘可访问性要求页面上的所有可交互组件均可通过键盘操作,且应尽量与系统自带组件的操作习惯保持一致。在实践的过程中,主要用到的知识点就是 tabindex 。什么是 tabindex 呢?
在 web 页面中,我们把用户按下 Tab 键可到达的元素列表称为 tab 队列。HTML 中默认只有 a、button 和 form 元素包含在 Tab 队列中(不同浏览器可能会有些差异)。如果想把其他元素添加至 Tab 队列,就需要通过 设置 tabindex 来实现了。tabindex 可以设为多种值:
tabindex = 负值(通常写为 tabindex="-1"):元素是可聚焦的,即可通过 element.focus() 聚焦,但是不能通过 Tab 键来访问到该元素,用 JavaScript 做页面小组件内部键盘导航的时候非常有用。
tabindex = 0:元素是可聚焦的,并且可以通过 Tab 键聚焦到该元素。如果多个元素拥有相同的 tabindex,它们的相对顺序按照他们在当前 DOM 中的先后顺序决定,所以保持 DOM 结构和视觉顺序一致很有必要。
tabindex = 正值:元素是可聚焦的,并且可以通过 Tab 键聚焦到该元素。tabindex 值越大在队列中越靠前,一般建议赋值不要超过0。
具体实现
接下来我们按照残障用户通过键盘或者辅助设备使用页面的步骤,结合 Element 中的一些组件,详细讲解如何为组件提升键盘可访问性。
到达组件
确定组件内可聚焦元素列表
在 HTML中 disabled 的 input 元素会被移出 tab 序列且不可聚焦。移除 disabled 元素的可聚焦性有利有弊,利在于可以减少键盘操作的复杂性,弊在于对于一些依靠 move focus 来阅读屏幕的用户,disabled 元素相当于被隐藏了。因此需要根据具体的场景具体分析,但是不管如何选择,建议整个系统保持一致。
Element 组件选择的是将 disabled 元素移除可聚焦性,也就是说在组件内进行焦点管理时,disabled 元素不会被计算进来。比如,对于可选择的 Tree 组件,若某个树节点被禁用了,那么它是不包含在焦点管理中的。
确定初始化焦点元素
在 Element 中,每个组件有且仅有一个可聚焦元素出现在 Tab 列表中。具体哪个元素被包含在 Tab 列表,获得初始化焦点,可以视情况而定:可以是最后一次焦点离开时的焦点元素,也可以是组件中被选中的第一个元素。
对于 Tree 组件,默认情况下,我们为第一个树节点设置了 tabindex = 0,即通过 Tab 键会导航至第一个树节点;而对于可选择的 Tree (即每个树节点前有一个 checkbox 框,供用户选择),我们则为第一个被选中的树节点设置了 tabindex = 0。
在组件内切换元素
焦点进入组件内部后,用户可以使用方向键在组件内可聚焦元素之间导航,此时需要进行组件内焦点管理,具体的实现是通过监听组件内的 keydown 事件,根据 keyCode 的值完成焦点转移;
焦点管理应当遵循以下原则:
可视化:focus 元素和一般元素在视觉上应有区别,方便用户定位当前焦点元素,开发时尽量不要使用以下 CSS 去除默认 focus 样式:
*:foucs { outline: 0 none; }
可持续性:焦点应永远保持在页面上的一个可交互组件内,同时在当前焦点元素的可视化发生改变或者被移除时进行焦点移交。比如 Element 中的 Dialog 组件,当它被关闭时,我们会把焦点元素重置回打开该 Dialog 的 Button 元素,避免焦点丢失。
可预测性:用户可以预测当按下一个键时,下一个焦点元素的位置,逻辑上符合用户的使用习惯。如用户在 Tree 组件中按下上下键时,焦点元素就会朝上下方向进行移动。
焦点管理有以下两种方法,各有利弊,视情况选择。比如 Element 的 Tree 组件选择的是下面第一种方式,而Autocomplete 组件选择的是第二种,两种组件的不同选择是基于最初组件的实现方式来衡量的:
使用 tabIndex 管理组件内的元素
1、给焦点初始元素设置 tabindex = 0, 其他的可聚焦元素设置属性 tabindex = -1
2、当在组件内按下方向键时,设置下一个元素的 tabindex = 0, 其他元素 tabindex = -1;然后执行element.focus()
3、用户按下 Tab 键,焦点准备离开组件时,如果设计要求下一次组件重聚焦时,焦点元素为某个特定元素的话,则设置该元素 tabindex = 0,其他元素 tabindex = -1
使用 tabIndex 管理焦点的一个好处是,浏览器会把当前 focus 元素滚动到 view 区域。
使用 aria-activedescendant 管理焦点。这种方式需要有 ARIA 的基础知识,因为篇幅限制就不展开描述了。
操作组件内元素
当用户定位到想要操作的元素时,接下来就是操作元素,如点击、选中等。我们应当尽可能地为用户的键盘操作提供便利。我们通过以下几方面进行介绍:
是否在获得焦点(focus)时自动选中(selected)
focus 就像鼠标的 hover,意味着当前元素可能被选中,也可能不被选中;而 selected 表示当前元素是已选择状态。一个组件内的子元素可以被 focus,也可以被 selected,甚至可以两种状态共存,所以要注意区分两种状态的视觉效果。
在 Element 中,我们对 RadioGroup 组件进行了处理,让它的 selected 状态跟随 focus,即:随着焦点的移动,元素也将被选择,通过可聚焦元素的 focus 和 blur 事件来触发元素的 selectd 状态。但是有些情况下,这对可访问性也是有害的,有时元素被选择时会引起页面刷新、跳转或者发起请求等超出用户预知的行为,如 Tabs 面板切换伴随 Ajax 请求等,就不适合将两种状态进行绑定。
此外,可选择的 Tree 组件也不适合将两种状态绑定。我们的做法是,当树节点获得焦点时,通过监听 keydown 事件,判断 keyCode 值为 space/enter 键时,才触发它的 selectd 状态。
为鼠标操作提供键盘支持
为 hover、click、mousedown 等鼠标事件提供相应的 focus 、blur、keydown 事件。比如 Tree 组件的树节点 click 事件可由 keydown 监听 space/enter 键来触发。
快捷键操作
在满足基本的键盘操作之后,适当地为组件添加快捷键能够有效地提高用户的操作效率,如通过 Esc 键用来关闭对话框和弹层,通过 space/enter 键用来触发元素的 click 事件等。设置快捷键时应注意以下几点:
应当避免和系统自带的、AT设备常用的、浏览器自带的快捷键冲突;
应注意快捷键不应太多,否则会给用户带来认知负荷;
将快捷键的分配文档提供给用户;
注意事项
通过 tabindex 方法管理组件内焦点时,焦点聚焦应使用 element.focus(),避免使用 createEvent、 dispatchEvent 这种方式。
使用 keydown 来捕获键盘事件,因为 IE 浏览器不会触发非字母的 keypress 事件。
鼠标点击可聚焦元素时会使得元素保留 focus 状态,此时我们原本希望仅在键盘 focus 才有的 :focus 样式会显现出来,影响正常用户的视觉效果。如果不期望达到此效果,需设法绕过。
结语
目前 Element 只是实现了大部分组件的键盘可操作性,还有很多不足之处,如 focus 状态的视觉样式不统一,以及快捷键设置需要后续进一步优化等。欢迎各位开发者围观指导,不吝赐教。
本项目地址:https://github.com/ElemeFE/element
最后,为你推荐
关于本文
作者:饿了么大前端@马冉